/*
 *  Brno University of Technology
 *
 *  Copyright (C) 2009-2010 by Brno University of Technology and the contributors
 *
 *  Complete list of developers available at our web site:
 *
 *       http://spl.utko.feec.vutbr.cz
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package cz.vutbr.feec.utko.ef.gp.tree;

import java.io.Serializable;
import java.util.Arrays;

import cz.vutbr.feec.utko.ef.grammar.Rule;
import cz.vutbr.feec.utko.ef.individuals.ActionTree;
import cz.vutbr.feec.utko.ef.individuals.DummyAction;

/**
 * The Class Node is a building block of chromozomes and represents a trees in a
 * program. The role of Nodes are to construct tree structure such as depicted in
 * figure \image html NodeTree.png
 */
public class Node implements Cloneable, Serializable {
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -5329774349827941100L;
	
	/** The parent. */
	private Node parent;
	
	/** The parent index. */
	private int parentIndex;

	/** The children. */
	private Node[] children;

	/** The weight. */
	private int weight = 1;
	
	/** The sub tree heights. */
	private int[] subTreeHeights;

	/** The action. */
	private ActionTree action;

	/** The deep. */
	private int deep;

	/** The token name. */
	private String tokenName;
	
	/** The node id. */
	public int nodeId;

	/**
	 * Instantiates a new node.
	 * 
	 * @param parent
	 *            the parent
	 * @param parentIndex
	 *            the parent index
	 * @param rule
	 *            the rule
	 */
	protected Node(Node parent, int parentIndex, Rule rule) {
		this.parentIndex = parentIndex;
		int length = rule.getChildTokens().length;
		children = new Node[length];
		subTreeHeights = new int[length];
		if (rule.getAction() != null) {
			try {
				if (!rule.getAction().isInputParameter()) {
					// clone action and assign new values
					action = (ActionTree) rule.getAction().clone();
					if (isTerminal()) {
						action.initialize();
					}
				} else {
					// only a single action is required 
					action = rule.getAction();
				}
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
		}
		this.parent = parent;
		if (parent != null) {
			deep = parent.getDeep() + 1;
		} else {
			deep = 0;
		}
		this.tokenName = rule.getFromToken();

		if (action == null && getChildren().length == 1) {
			action = new DummyAction();
		}

		if (action == null) {
			throw new RuntimeException("Rule " + rule
					+ " has not defined action but it requires to have.");
		}

	}

	/**
	 * Instantiates a new node.
	 * 
	 * @param rule
	 *            the rule
	 */
	protected Node(Rule rule) {
		this(null, -1, rule);
	}

	/**
	 * Clone deep.
	 * 
	 * @param parent
	 *            the parent
	 * 
	 * @return the node
	 */
	protected Node cloneDeep(Node parent) {
		Node res = null;
		try {
			res = (Node) this.clone();
			assert this != res;
			res.subTreeHeights = subTreeHeights.clone();
			res.children = new Node[children.length];
			if (this.action != null) {
				res.action = (ActionTree) this.action.clone();
			}
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		res.parent = parent;
		for (int i = 0; i < res.children.length; i++) {
			res.children[i] = null;
		}
		return res;
	}

	/**
	 * Gets the action.
	 * 
	 * @return the action
	 */
	public ActionTree getAction() {
		return action;
	}

	/**
	 * Gets how deep in the tree this node is. I.e. the root node is at deep
	 * zero. All its children has deep equal one and their children have deep
	 * two, etc.
	 * 
	 * @return the deep in the chromozome tree.
	 */
	public int getDeep() {
		return deep;
	}

	/**
	 * Gets the height of the node. This height is computed as a maximum of
	 * subtree heights.
	 * 
	 * @return the height
	 */
	public int getHeight() {
		int max = 0;
		for (int v : subTreeHeights) {
			if (max < v) {
				max = v;
			}
		}
		return max + 1;
	}

	/**
	 * Gets an array of the node children.
	 * 
	 * @return the children
	 */
	public Node[] getChildren() {
		return children;
	}

	/**
	 * Gets the nonterminal name.
	 * 
	 * @return the nonterminal name
	 */
	public String getNonterminalName() {
		return tokenName;
	}

	/**
	 * Gets the parent node of the current node.
	 * 
	 * @return the parent
	 */
	public Node getParent() {
		return parent;
	}

	/**
	 * Get the index of the children subtree this node is member.
	 * 
	 * @return the parent index
	 */
	public int getParentIndex() {
		return parentIndex;
	}
	
	/**
	 * Gets the result.
	 * 
	 * @return the result
	 */
	public Object getResult() {
		if (isTerminal()) {
			// return directly its value, end of recursion
			return getAction().getValue();
		} else {
			// compute operand values
			Object operands[] = new Object[children.length];
			for (int i = 0; i < children.length; i++) {
				// step into recursion
				operands[i] = children[i].getResult();
			}
			// return from recursion
			return getAction().getValue(operands);
		}
	}

	/**
	 * Gets the rule.
	 * 
	 * @return the rule
	 */
	public Rule getRule() {
		return action.getRule();
	}

	/**
	 * Get the height of subtrees. The index of the current subtree at parent
	 * can be obtained with getParentIndex().
	 * 
	 * @param index
	 *            of the required tree,
	 * 
	 * @return the sub tree height
	 */
	public int getSubTreeHeight(int index) {
		assert index < subTreeHeights.length : "In the node " + this
				+ " is not subtree with index " + index;
		return subTreeHeights[index];
	}

	/**
	 * Gets the weight of node. The weight stands for number of nodes the
	 * sub-tree contains (including itself), i.e. possible values are 1 to
	 * infinity.
	 * 
	 * @return the weight of the subtree the node is root.
	 */
	public int getWeight() {
		return weight;
	}

	/**
	 * Checks if is input parameter.
	 * 
	 * @return true, if is input parameter
	 */
	public boolean isInputParameter() {
		return action.isInputParameter();
	}

	/**
	 * Checks if is root.
	 * 
	 * @return true, if is root
	 */
	public boolean isRoot() {
		return parent == null;
	}

	/**
	 * Checks if is terminal.
	 * 
	 * @return true, if is terminal
	 */
	public boolean isTerminal() {
		return children.length == 0;
	}

	/**
	 * Sets the deep.
	 * 
	 * @param deep
	 *            the new deep
	 */
	public void setDeep(int deep) {
		this.deep = deep;
	}

	/**
	 * Sets the child.
	 * 
	 * @param newNode
	 *            the new node
	 * @param index
	 *            the index
	 */
	public void setChild(Node newNode, int index) {
		children[index] = newNode;
	}

	/**
	 * Sets the parent.
	 * 
	 * @param node
	 *            the new parent
	 */
	public void setParent(Node node) {
		this.parent = node;
	}

	/**
	 * Sets the parent index.
	 * 
	 * @param parentIndex the new parent index
	 */
	public void setParentIndex(int parentIndex) {
		this.parentIndex = parentIndex;
	}

	/**
	 * Sets the sub tree height.
	 * 
	 * @param index
	 *            the index
	 * @param value
	 *            the value
	 */
	protected void setSubTreeHeight(int index, int value) {
		this.subTreeHeights[index] = value;
	}

	/**
	 * Sets the weight.
	 * 
	 * @param weight
	 *            the new weight
	 */
	protected void setWeight(int weight) {
		this.weight = weight;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "[" + tokenName + "-DEEP:" + deep + ",HEIGHT:"
				+ Arrays.toString(subTreeHeights) + ", WEIGHT:" + weight + ", "
				+ action.getRule() + "]";
	}
}
